// PlayerKernel.cpp
 
// Implementacia jadra hraca, teda zakladnych funkcii pre jeho funkcionalitu

#include "Player/PlayerKernel.h"
//#include "Player/PlayerSkills.h"
#include "Common/Interfaces/MsgProcess.h"
#include "Common/Interfaces/Commands.h"
#include "Common/Tasks.h"
#include "Common/Support/Debug.h"
#include "Common/Protocol/CommandGen.h"

//#include <stdio.h>
//#include <math.h>
#include <string.h>

#include "Evaluating/EvaluatingPanesMap.h"
#include "Evaluating/EvaluatingPlayerList.h"

/*
#include <string>
#include <fstream>

using namespace std;
*/

#ifdef WIN32
#define NOMINMAX
#include <windows.h>
#endif

//model sveta
World* world = new World();

//rozhranie pre posielanie prikazov na server
CommandsInterface* commandGenerator;

// Ohodnocovacia mapa
extern CEvaluatingPanesMap* ePanesMap;

// Zoznam ostatnych hracov spolu s informaciami o nich, ktorych vidi tento hrac
extern CEvaluatingPlayerList* ePlayerList;

// Ohodnocovat policka?
extern bool EvaluatingPanes;

//premenna, ktora sa incializuje parameter -test, znamena ze sa bude pustat GetActTestBehavior()->Behave();
extern bool isUnitTestingEnabled;


// Konstruktor
PlayerKernel::PlayerKernel(const char* name, const char* desc) : Module(name, desc, "v0.2.000")
{
	// Inicializuj si tento svet
	world->me->pos.SetX(-37);
	world->state->SetPlayMode(PM_BeforeKickOff);

	DATA_STORAGE.lastValidBallInfo = NULL;
	DATA_STORAGE.lastValidBallTime = -1;
	
	// Inicializovanie pointrov k poslednej informacii o sense_body a vizualnej informacii	
	lastSb = NULL;
	lastVi = NULL;
	lastViTime = 0;
	lastSbTime = 0;
	
	// Inicializovanie synchronizacie
	synchMode = Synch_Normal;
	DATA_STORAGE.nextSynchMode = Synch_Normal;
	decisionWasMade = true;

	// Inicializovanie udajov k spatnej vazbe hraca na 0
	feedback = new FEEDBACK_DATA;
	memset((void*) feedback, 0, sizeof(FEEDBACK_DATA));

	// Inicializovanie struktury na pocitanie prikazov na 0 
	counts = new COMMAND_COUNTS;
	memset((void*) counts, 0, sizeof(COMMAND_COUNTS));

	Main::GetInstance()->RegisterModule(this, MEMORY_TASK);
	Main::GetInstance()->RegisterModule(this, SYNCHRONIZATION_TASK);
	Main::GetInstance()->RegisterModule(this, TACTICS_TASK);
	World::BindInterfaces();

	commandGenerator = (CommandsInterface*) Main::GetInstance()->GetInterface(COMMANDS_TASK);
}

// Destruktor
PlayerKernel::~PlayerKernel()
{
	if(counts != NULL)
		delete counts;

	if(feedback != NULL)
		delete feedback;

	if(world != NULL)
		delete world;
}

// Implementacia rozhrania
Interface* PlayerKernel::GetInterface(int task) const
{
	if(task == SYNCHRONIZATION_TASK)
		return (SynchronizationInterface*) this;
	else if(task == MEMORY_TASK)
		return (MemoryInterface*) this;
	else if(task == TACTICS_TASK)
		//takticke rozhranie ma iba hrac implementovane
#ifdef GOALIE
		return NULL;
#else
		return (TacticsInterface*) this;
#endif
	else 
		return NULL;
}

// Funkcia pre predpovedanie informacii o lopte v nasledujucich nSteps cykloch
void PlayerKernel::ClearPredictedBallStates(int nSteps)
{
	int nIndex = DATA_STORAGE.actualBallStateIndex;

	for(int i = 1; i <= nSteps; i++)
	{
		if(++nIndex >= BALL_HISTORY_COUNT)
			nIndex = 0;

		if(DATA_STORAGE.ballStates[nIndex] != NULL)
		{
			delete DATA_STORAGE.ballStates[nIndex];
		}

		DATA_STORAGE.ballStates[nIndex] = NULL;
	}
}

// Predikuje stavy lopty nSteps cyklov dopredu
void PlayerKernel::PredictBallStates(Ball* actBallInfo, int nSteps)
{
	int nIndex = DATA_STORAGE.actualBallStateIndex;

	for(int i = 1; i <= nSteps; i++)
	{
		if(++nIndex >= BALL_HISTORY_COUNT)
			nIndex = 0;

		if(DATA_STORAGE.ballStates[nIndex] != NULL)
		{
			delete DATA_STORAGE.ballStates[nIndex];
		}

		Ball* tmpBall = new Ball(*actBallInfo);
	
		Point tmpPos = tmpBall->pos;
		Vector tmpVect = tmpBall->vel;
		
		for(int j = 1; j <= i; j++)
		{
			tmpVect = tmpBall->vel * float(1.0 - pow(world->params->server->ball_decay, j)) / float(1.0 - world->params->server->ball_decay);
			tmpPos += tmpVect;
		}

		tmpBall->pos = tmpPos;
		tmpBall->vel = tmpBall->vel * float(1.0 - pow(world->params->server->ball_decay, i)) / float(1.0 - world->params->server->ball_decay);
				
		tmpBall->SetValid(false);
		tmpBall->SetAge(0);
			
		DATA_STORAGE.ballStates[nIndex] = tmpBall;
	}
}

// Pri prichode spravy hear
void PlayerKernel::OnHear(int time, float direction, bool ourTeam, int uniform, const char* msg)
{
	GetEventHandlers()->OnHear(time, direction, ourTeam, uniform, msg);
}

// Pri zmene herneho modu
void PlayerKernel::OnChangeMode(int time, PlayMode mode)
{
	// Pretazene v PlayerTactics
	PlayMode currMode = world->state->GetPlayMode();

	if((currMode == PM_BeforeKickOff && mode == PM_KickOff_Our) ||
		(currMode == PM_BeforeKickOff && mode == PM_KickOff_Their) ||
		(currMode == PM_AfterGoal_Our && mode == PM_KickOff_Their) ||
		(currMode == PM_AfterGoal_Their && mode == PM_KickOff_Our))
	{
		
		// Teraz urcite pozname presnu poziciu lopty, je totiz v strede
		world->ball->pos = Point(0, 0);
		world->ball->vel = RectVector(0, 0);
		world->ball->SetAge(0);
		world->ball->SetValid();
	}

	world->state->SetPlayMode(mode);

#ifndef GOALIE
	GetEventHandlers()->OnChangeMode(time, mode);
#endif
}

// Pri zmene typu hraca
void PlayerKernel::OnChangePlayerType(int uniform, int type)
{
	world->SetPlayerType(OUR, uniform, type);
	if (uniform==world->me->GetUniform())
	{
		world->me->SetSide(OUR);
		world->me->SetType(type);
	}
}

// Pri prichode spravy init
void PlayerKernel::OnInit(RealSide realSide, int uniform, PlayMode mode)
{
	myside = realSide;
	// Pretazene v PlayerSkills
	TXTN("Connected, %s side, uniform %i", realSide == LEFT ? "left" : "right", uniform);

	if(EvaluatingPanes)
	{
		ePanesMap->InitEvaluatingPanesMap(true);
	}

#ifdef WIN32
	char str[20];
	sprintf(str,"#%d", uniform);
	SetConsoleTitle(str);
#endif

	// Nastavenie vymeny stran
	CommandsInterface *commandGen = (CommandsInterface *) Main::GetInstance()->GetInterface(COMMANDS_TASK);
	if(commandGen != NULL)
		commandGen->SetRealSide(realSide);
	
	MsgProcessInterface *parser = (MsgProcessInterface *) Main::GetInstance()->GetInterface(MESSAGE_PROCESS_TASK);
	if(parser != NULL)
		parser->SetRealSide(realSide);
	
	MsgProcessInterface *clParser = (MsgProcessInterface *) Main::GetInstance()->GetInterface(CLANG_MESSAGE_PROCESS_TASK);
	if(clParser != NULL)
		clParser->SetRealSide(realSide);

	// Toto je pozicia noveho pripojeneho hraca nastavena serverom
	world->me->pos = Point(- uniform * 3.f, -37);
	world->me->SetUniform(uniform);
	world->state->SetPlayMode(mode);

	GetTurnNeckToBehaviour()->NeckSynchronizationWithVI(NAR_VIEW);
	DATA_STORAGE.SetNextSynchMode(Synch_VIOnly);

#ifndef GOALIE
	GetEventHandlers()->OnInit(realSide, uniform, mode);
#endif
}

// Handler pre informacie o heterogennych typoch hracov
void PlayerKernel::OnPlayerParam(const PLAYER_PARAMS* params)
{
	world->params->ProcessPlayerParams(params);
}

// Handler pre informacie o jednom heterogennom (alebo normalnom) type hraca
void PlayerKernel::OnPlayerType(int id, const PLAYER_TYPE* type)
{
	world->params->ProcessPlayerType(id, type);
}

// Pri prichode spravy s informaciou o skore
void PlayerKernel::OnScore(int time, int our, int their)
{
	world->state->SetScore(our, their);
}

/*static void VIDUMP(const VISUAL_INFO* vi)
{
	int i;
		
	DBGN("--> VI DUMP start");

	DBGN("  LANDMARKS:");
	for(i = 0; i < vi->landmarkCnt; i++)
	{
		DBGN("    type %i dist %3.2f dir %3.2f distch %3.2f dirch %3.2f", vi->landmarks[i].type, vi->landmarks[i].dist, vi->landmarks[i].dir, vi->landmarks[i].distChange, vi->landmarks[i].dirChange);
	}

	DBGN("  LINES:");
	for(i = 0; i < vi->lineCnt; i++)
	{
		DBGN("    type %i dist %3.2f dir %3.2f", vi->lines[i].type, vi->lines[i].dist, vi->lines[i].dir);
	}

	DBGN("<-- VI DUMP end");
}*/

// Pri prichode zrakovej spravy see
void PlayerKernel::OnSee(const VISUAL_INFO* vi)
{
//	VIDUMP(vi);

	// Dosla VI, nastavime priznak na okamzite poslanie prikazu TurnNeck na server
	world->me->bSendTurnNeckTo = true;

	lastVi = vi;
	lastViTime = lastVi->time;

	// Ak nemame este spravu o sense_body, tak este nic nerob.
	if(lastSb == 0)
		return;

	// Ak dosla v novom cykle skor vizualna informacia, tak pockaj na sense_body
	if(lastVi->time > lastSb->time)
		return;

	// Zmen parametre sveta bez urobenia rozhodnuti (normal mod)
	world->ProcessInfo(lastSb, lastVi);

	// Prave hrac dostal skutocnu informaciu o videnej lopte
	if(vi->ballCnt != 0 && vi->ball.distValid)
	{
		if(DATA_STORAGE.ballStates[DATA_STORAGE.actualBallStateIndex] != NULL)
			delete DATA_STORAGE.ballStates[DATA_STORAGE.actualBallStateIndex];

		Ball* tmpBall = new Ball(*world->ball);
		DATA_STORAGE.ballStates[DATA_STORAGE.actualBallStateIndex] = tmpBall;
	
		if(ePlayerList->AnyoneInAreaRadius(DATA_STORAGE.ballStates[DATA_STORAGE.actualBallStateIndex]->pos, 1.5f))
		{
			ClearPredictedBallStates((BALL_HISTORY_COUNT - 1) / 2);
		}
		else
		{
			// Predikcia spravania lopty na BALL_HISTORY_COUNT miest
			PredictBallStates(DATA_STORAGE.ballStates[DATA_STORAGE.actualBallStateIndex], (BALL_HISTORY_COUNT - 1) / 2);		
		}

		if(DATA_STORAGE.lastValidBallInfo != NULL)
			delete DATA_STORAGE.lastValidBallInfo;

		DATA_STORAGE.lastValidBallInfo = new Ball(*world->ball);
		DATA_STORAGE.lastValidBallTime = world->state->GetSimTime();
	}
	
	// DEBUG VI
 /*
	DBG("\n[%d. cyklus]\n", world->state->GetSimTime());
	DBG("Hrac %d na suradniciach [%lf.2, %lf.2]\n\n",world->me->GetUniform(), world->me->pos.GetX(), world->me->pos.GetY());

	int i;

	// NASI HRACI
	for(i = 0; i < vi->oursCnt; i++)
	{
		if(vi->ours[i].valid)
		{
			DBG("[NAS] Hrac %d je vo vzdialenosti %lf pod uhlom %lf)\n", vi->ours[i].uniformNumber, vi->ours[i].dist, vi->ours[i].dir);
		}
	}

	// SUPEROVI HRACI
	for(i = 0; i < vi->theirCnt; i++)
	{
		if(vi->their[i].valid)
		{
			DBG("[SUPER] Hrac %d je vo vzdialenosti %lf pod uhlom %lf)\n", vi->their[i].uniformNumber, vi->their[i].dist, vi->their[i].dir);
		}
	}

	for(i = 0; i < vi->nearbyCnt; i++)
	{
		if(vi->nearby[i].valid)
		{
			DBG("[BLIZKY] Hrac %d je vo vzdialenosti %lf pod uhlom %lf)\n", vi->nearby[i].uniformNumber, vi->nearby[i].dist, vi->nearby[i].dir);
		}
	}
	

	// NEZNAMI HRACI
	for(i = 0; i < vi->unknownCnt; i++)
	{
		if(vi->unknown[i].valid)
		{
			DBG("[NEZNAMY] Hrac %d je vo vzdialenosti %lf pod uhlom %lf)\n", vi->unknown[i].uniformNumber, vi->unknown[i].dist, vi->unknown[i].dir);
		}
	}

// */
	if(synchMode != Synch_Normal && !decisionWasMade)
	{ 
		if (isUnitTestingEnabled == true)
		{
			GetActTestBehaviour()->Behave();
		}
		else {
			GetActBehaviour()->Behave();				
		}
		decisionWasMade = true;
	}

	lastVi = NULL;
}

// Pri prichode spravy sense_body
void PlayerKernel::OnSenseBody(const SENSE_BODY* sb)
{
	static int sbCount = 0;
	sbCount++;

	// Index pre kruhovy zoznam, po presiahnuti pola sa nastavi na jeho prvu bunku
	if(++DATA_STORAGE.actualBallStateIndex >= BALL_HISTORY_COUNT)
		DATA_STORAGE.actualBallStateIndex = 0;

	PlayMode pm = world->state->GetPlayMode();
	
	if(sbCount % 3 != 0 &&
	   (pm == PM_Offside_Our || pm == PM_Offside_Their || pm == PM_CatchFault_Our || pm == PM_CatchFault_Their ||
		pm == PM_BackPass_Our || pm == PM_BackPass_Their || pm == PM_FreeKickFault_Our || pm == PM_FreeKickFault_Their))
	{
		world->state->SetSimTime(world->state->GetSimTime() + 1);
		DATA_STORAGE.SetNextSynchMode(Synch_Normal);
	}

// EP 
	if(EvaluatingPanes)
	{
		// Prisla sprava senseBody, treba inkrementovat vek informacii o hracoch
		ePlayerList->IncrementPlayerInformationAge();

/*		DBG("\n\n/------------ %d cyklus -----------\\ \n", world->state->GetSimTime());
		DBG("Ja som na suradniciach XY (%lf.2, %lf.2)\n\n",world->me->pos.GetX(), world->me->pos.GetY());

		int nInfoCount = 0;

		for(ePlayerList->evPlayerIterator = ePlayerList->GetEvPlayerIteratorBegin(); ePlayerList->evPlayerIterator != ePlayerList->GetEvPlayerIteratorEnd(); ePlayerList->evPlayerIterator++)
		{		
			CEvaluatingPlayer* evPlayer = (*ePlayerList->evPlayerIterator);
		
			if(evPlayer->GetPlayerType() == PT_OUR)
			{
				DBG("Informacia o nasom hracovi (stara %d cykly)\n", evPlayer->GetPlayerInformationAge());
				nInfoCount++;
			}
			else if(evPlayer->GetPlayerType() == PT_THEIR)
			{
				DBG("Informacia o superovom hracovi (stara %d cykly)\n", evPlayer->GetPlayerInformationAge());
				nInfoCount++;
			}
			else if(evPlayer->GetPlayerType() == PT_NEARBY)
			{
				DBG("Informacia o blizkom hracovi (stara %d cykly)\n", evPlayer->GetPlayerInformationAge());
				nInfoCount++;
			}
			else if(evPlayer->GetPlayerType() == PT_UNKNOWN)
			{
				DBG("Informacia o neznamom hracovi (stara %d cykly)\n", evPlayer->GetPlayerInformationAge());
				nInfoCount++;
			}

			DBG("Hrac s cislom (%d) je na suradniciach XY (%lf, %lf) pod absolutnym uhlom (%lf)\n", evPlayer->GetPlayerIDNumber(), evPlayer->GetPlayerXPosition(), evPlayer->GetPlayerYPosition(), evPlayer->GetPlayerTurnAngle());
		}
		DBG("Pocet vizualnych informacii: (%d)\n", nInfoCount);

//		if(nInfoCount > 21)
//			DBG("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n");
*/	

	}
// EP/
	
	// Pri zmene simulacneho cyklu
	if(world->state->IsTimeStoped() || sb->time != world->state->GetSimTime())
	{
		// Uloz hracovu predchadzaujucu poziciu
		Point prevPos = world->me->pos;

		// Aplikuj vsetky prikazy, ktore hrac vykonal od predchadzajucej spravy sense_body
		world->ApplyFeedback(feedback);
		
		// Simuluj pohyb vsetkych objektov
		world->Step();
 
		// Uloz datovy pointer a cas na tuto sense_body spravu 
		lastSb = sb;
		lastSbTime = lastSb->time;

		// Synchronizacny mod
		synchMode = DATA_STORAGE.GetNextSynchMode();

		// Zmen hracov stav podla udajov zo sense_body
		world->me->SetStamina(lastSb->stamina);
		world->me->SetEffort(lastSb->effort);
		world->me->SetNeckDir(DegAngle(lastSb->headAngle));
		world->me->SetViewWidth(lastSb->viewWidth);
		
		// Zalohuj pocty prikazov
		memcpy((void*) counts, (void*) &lastSb->counts, sizeof(COMMAND_COUNTS));

		// Mame aj vizualnu informaciu?
		if(lastVi != NULL)
		{
			if(lastVi->time > world->state->GetSimTime())
			{
				// Vizualna informacia dosla skor ako sprava sense_body, pravdepodobne stara verzia servera
				world->state->SetSimTime(lastSb->time);
				world->ProcessInfo(lastSb, lastVi);
				lastVi = NULL;

				// Decision making
				if (isUnitTestingEnabled == true)
				{
					GetActTestBehaviour()->Behave();
				}
				else {
					GetActBehaviour()->Behave();				
				}
				decisionWasMade = true;
			}
		}
		else
		{
			// Este nemame vizualnu informaciu
			world->state->SetSimTime(lastSb->time);
			
			// Nastav zatial hracovu rychlost

			Angle angle = world->me->GetAbsDir() + world->me->GetNeckDir() + DegAngle(lastSb->speedDirection);
			world->me->vel = PolarVector(lastSb->speedSize, angle);
		
			// aj jeho poziciu
			world->me->pos = prevPos + world->me->vel / world->me->type->player_decay;
		
			// V normal mode zacni rozhodovanie
			if(synchMode == Synch_Normal)
			{ 
				if (isUnitTestingEnabled == true)
				{
					GetActTestBehaviour()->Behave();
				}
				else {
					GetActBehaviour()->Behave();				
				}
				decisionWasMade = true;
			}
			else
			{
				decisionWasMade = false;
			}
		}
	}
	else
	{
		// Prisla sprava sense_body, ale nenastala zmena simulacneho casu
		lastSb = sb;

		// Podla spravy sense_body zmen svoj svet
		world->me->SetStamina(lastSb->stamina);
		world->me->SetEffort(lastSb->effort);
		world->me->SetNeckDir(DegAngle(lastSb->headAngle));
		world->me->SetViewWidth(lastSb->viewWidth);
		
		// Zalohuj pocty prikazov
		memcpy((void*) counts, (void*) &lastSb->counts, sizeof(COMMAND_COUNTS));

		Angle angle = world->me->GetAbsDir() + world->me->GetNeckDir() + DegAngle(lastSb->speedDirection);
		world->me->vel = PolarVector(lastSb->speedSize, angle);
	}
}

// Handler pre zmenu parametrov servera
void PlayerKernel::OnServerParam(const SERVER_PARAMS* params)
{
	world->params->ProcessServerParams(params);
}

// 
void PlayerKernel::OnFreeform(const char* msg)
{
   // Treba naimplementovat - vybranie vhodnej akcie na zaklade obdrzanej "freeform" spravy
}

void PlayerKernel::OnExecDash(float power)
{
	if(power > world->params->server->maxpower)
	{
		power = world->params->server->maxpower;
	}
	else if(power < world->params->server->minpower)
	{
		power = world->params->server->minpower;
	}

	// Pohyb vpred
	if(power >= 0)
	{
		if(power > world->me->GetStamina() + world->me->type->extra_stamina)
		{
			power = world->me->GetStamina() + world->me->type->extra_stamina;
		}
	}
	// Pohyb vzad
	else
	{
		// Pohyb vzaj je 2x narocny na energiu
		if(fabsf(power * 2) > world->me->GetStamina() + world->me->type->extra_stamina)
		{
			power = (world->me->GetStamina() + world->me->type->extra_stamina) / -2.0f;
		}
	}

	if(feedback->lastPower == 0)
		feedback->lastPower = power;
}

void PlayerKernel::OnExecKick(float power, float direction)
{
	if(power > world->params->server->maxpower)
	{
		power = world->params->server->maxpower;
	}
	else if(power < world->params->server->minpower)
	{
		power = world->params->server->minpower;
	}

	if(feedback->lastKickPower == 0)
	{
		feedback->lastKickPower = power;
		feedback->lastKickDir = direction;
	}
}

void PlayerKernel::OnExecNeck(int angle)
{
	if(angle > world->params->server->maxneckmoment)
	{
		angle = int(world->params->server->maxneckmoment);
	}
	else if(angle < world->params->server->minneckmoment)
	{
		angle = int(world->params->server->minneckmoment);
	}

	if(feedback->lastNeck == 0)
		feedback->lastNeck = angle;
}

void PlayerKernel::OnExecTurn(float moment)
{
	if(moment > world->params->server->maxmoment)
	{
		moment = world->params->server->maxmoment;
	}
	else if(moment < world->params->server->minmoment)
	{
		moment = world->params->server->minmoment;
	}

	if(feedback->lastTurn == 0)
		feedback->lastTurn = moment;
}


// Zmeni formaciu
void PlayerKernel::ChangeFormation(const char* formation)
{
    int unitcount1, unitcount2, unitcount3;

    unitcount1 = (int)(formation[9] - '0');
    unitcount2 = (int)(formation[10] - '0');
    unitcount3 = (int)(formation[11] - '0');

    DATA_STORAGE.formation.InitRawUnits(SIDE_LEFT, DATA_STORAGE.GetFormation(formation), GetUnit((char)formation[12]), 
		GetUnit((char)formation[13]), GetUnit((char)formation[14]));
	float offset = 2.0f*(formation[15]-'A') - FIELDWIDTH/2.0f;
	DATA_STORAGE.formation.SetUnitOffset((float)2.0f*(formation[15]-'A')- FIELDWIDTH/2.0f);
	TXTN("We are changing formation to %s", formation);
}
